﻿/*
 *	Copyright (c) 2009 Queensland University of Technology. All rights reserved.
 *	The QUT Bioinformatics Collection is open source software released under the 
 *	Microsoft Public License (Ms-PL): http://www.microsoft.com/opensource/licenses.mspx.
 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using QUT.Bio.Map2D;
using QUT.Bio.Graphs;
using QUT.Bio.Util;
using QUT.Bio.SilverMap.ObjectModel;

namespace QUT.Bio.SilverMap {

	// TODO: Node details panel.

	public partial class View {

		private double epsilon = 0.1;
		private PositionedElement centre;
		private PositionedElement thresholdElement;
		private RadiusManipulator thresholdRadius;
		private PositionedElement perimeter;
		private double threshold;
		private ViewModel model;

		public event Action ThresholdChanged;

		private static readonly SolidColorBrush MAP_PERIMETER_BRUSH = new SolidColorBrush( Colors.LightGray );
		private static readonly SolidColorBrush MAP_CENTRE_DOT_BRUSH = new SolidColorBrush( Colors.LightGray );
		private static readonly SolidColorBrush MAP_THRESHOLD_BRUSH = new SolidColorBrush( Colors.Red );

		// TODO: move to adapter.
		private void GraphReplaced () {
			if ( model.DataProducer == null ) {
				graphView.Nodes.Clear();
			}
			else {
				graphView.Nodes.ReplaceAll(
					from seq in model.SequenceSummaries
					select new Node<SequenceSummary>( seq )
				);
			}
		}

		// TODO: move to adapter.
		private void GraphUpdated () {
			if ( model.DataProducer == null ) {
				graphView.Nodes.Clear();
			}
			else {
				HashList<SequenceSummary> nodesToRemove = new HashList<SequenceSummary>();
				
				nodesToRemove.AddRange( from x in graphView.Nodes select x.Content );
				
				foreach ( SequenceSummary summary in model.SequenceSummaries ) {
					if ( graphView.Nodes.ContainsKey( summary ) ) {
						nodesToRemove.Remove( summary );
					}
					else {
						graphView.Nodes.Add( new Node<SequenceSummary>( summary ) ); 
					}
				}
				
				foreach ( SequenceSummary summary in nodesToRemove ) {
					graphView.Nodes.Remove( graphView.Nodes[summary] );
				}
				
				graphView.NodesChanged( graphView.Nodes );
			}
		}

		public void InitChildControls (
			ViewModel model
		) {
			this.model = model;

			centre = graphView.Canvas.Add( new Ellipse() {
				Fill = MAP_CENTRE_DOT_BRUSH,
				StrokeThickness = 0,
				Width = 14,
				Height = 14,
			}, 0, 0, -7, -7, true, false );

			perimeter = graphView.Canvas.Add( new Ellipse() {
				Fill = new SolidColorBrush( Colors.Transparent ),
				Stroke = MAP_PERIMETER_BRUSH,
				StrokeThickness = 2,
				Width = 2,
				Height = 2
			}, 0, 0, -1, -1, false, true );

			InitializeStretcher( stretchSlider );
			InitializeThreshold( thresholdSlider );
			InitializeZoomSlider();
			model.Normalizer.PropertyChanged += ListenToNormalizer;
			model.DataCollectionLoaded += GraphReplaced;
			model.DataCollectionModified += GraphUpdated;
			model.ReferenceSequenceUpdated += ReferenceSequenceUpdated;
		}

		public void ReferenceSequenceUpdated (){
			graphView.UpdateLocations( true );
		}

		public double Epsilon {
			get {
				return epsilon;
			}
			set {
				epsilon = value;
			}
		}

		#region Property: Threshold
		// TODO: convert Threshold/thresholdRadius/Threshold slider into something better encapsulated to remove explicit dependency on model.Normalizer.

		/// <summary>
		/// Get or set the threshold (which is the radius of the red circle in a SilverMap application.
		/// </summary>

		public double Threshold {
			get {
				return threshold;
			}
			set {
				if ( threshold != value ) {
					threshold = value;
				}

				thresholdRadius.Radius = model.Normalizer.Normalize( threshold );
			}
		}
		#endregion

		private double RadiusTransform ( double x ) {
			return epsilon + ( 1 - epsilon ) * x;
		}

		#region Method: InitializeStretcher
		/// <summary>
		/// Saves a reference to the supplied labelled slider and
		/// binds it to the stretcher housed in the model.
		/// </summary>
		/// <param name="stretchSlider">A LabelledSlider that will be bound to the stretcher.</param>

		private void InitializeStretcher ( LabelledSlider stretchSlider ) {
			this.stretchSlider = stretchSlider;

			stretchSlider.Label = "Stretch:";
			stretchSlider.ValueConverter = delegate( double x ) {
				return x.ToString( "f2" );
			};
			stretchSlider.Bind( model.Stretcher, "Stretch", "Minimum", "Maximum" );
			model.Stretcher.Stretch = 0.5;

			model.Stretcher.PropertyChanged += delegate( object sender, PropertyChangedEventArgs args ) {
				if ( args.PropertyName == "Stretch" ) {
					graphView.UpdateLocations( false );

					if ( thresholdSlider != null ) {
						thresholdRadius.Radius = model.Normalizer.Normalize( threshold );
					}
				}
			};
		}
		#endregion

		#region Method: ListenToNormalizer
		// TODO: Move to adapter
		private void ListenToNormalizer ( object sender, PropertyChangedEventArgs args ) {
			if ( args.PropertyName == "Minimum" || args.PropertyName == "Maximum" ) {
				graphView.UpdateLocations( false );
			}
		}
		#endregion

		public void InitializeThreshold ( LabelledSlider thresholdSlider ) {
			this.thresholdSlider = thresholdSlider;

			Ellipse t = new Ellipse() {
				Fill = new SolidColorBrush( Colors.Transparent ),
				Stroke = MAP_THRESHOLD_BRUSH,
				StrokeThickness = 1,
				Width = 2,
				Height = 2
			};
			thresholdElement = graphView.Canvas.Add( t, 0, 0, -0.5, -0.5, false, true );
			thresholdRadius = new RadiusManipulator( thresholdElement, 0, 1, RadiusTransform );

			thresholdSlider.Label = "Radius:";

			thresholdSlider.ValueConverter = delegate( double v ) {
				return model.Normalizer.Denormalize( v ).ToString( "e2" );
			};

			thresholdRadius.Radius = 0.5;
			thresholdSlider.Bind( thresholdRadius, "Radius", "Minimum", "Maximum" );
			Threshold = model.Normalizer.Denormalize( thresholdRadius.Radius );

			thresholdRadius.PropertyChanged += delegate( object sender, PropertyChangedEventArgs args ) {
				if ( args.PropertyName == "Radius" ) {
					threshold = model.Normalizer.Denormalize( thresholdRadius.Radius );

					if ( ThresholdChanged != null ) {
						ThresholdChanged();
					}
				}
			};
		}

		public Brush ThresholdStroke {
			get {
				return ( thresholdElement.Element as Ellipse ).Stroke;
			}
			set {
				( thresholdElement.Element as Ellipse ).Stroke = value;
			}
		}

		public Brush PerimeterStroke {
			get {
				return ( perimeter.Element as Ellipse ).Stroke;
			}
			set {
				( perimeter.Element as Ellipse ).Stroke = value;
			}
		}

		public Brush CentreDotFill {
			get {
				return ( centre.Element as Ellipse ).Fill;
			}
			set {
				( centre.Element as Ellipse ).Fill = value;
			}
		}

		public Brush CentreDotStroke {
			get {
				return ( centre.Element as Ellipse ).Stroke;
			}
			set {
				( centre.Element as Ellipse ).Stroke = value;
			}
		}

	}
}
